{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "1. Import libraries"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "import yfinance as yf\n",
    "import sklearn\n",
    "from scipy.stats import loguniform, randint, uniform\n",
    "from sklearn.linear_model import Ridge, Lasso\n",
    "from sklearn.neighbors import KNeighborsRegressor\n",
    "from sklearn.model_selection import train_test_split, TimeSeriesSplit, cross_val_score, RandomizedSearchCV\n",
    "from sklearn.ensemble import VotingRegressor\n",
    "from sklearn.metrics import make_scorer"
   ]
  },
  {
   "cell_type": "markdown",
   "source": [
    "2. Define functions to load data, create features, create target, and scoring function."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "outputs": [],
   "source": [
    "def create_features(df):\n",
    "    df['Spread'] = df['High'] - df['Low']\n",
    "    df['Gap'] = df['Open'] - df['Close'].shift(1)\n",
    "    df['Intraday'] = df['Open'] - df['Close']\n",
    "    return df\n",
    "\n",
    "def drop_features(df):\n",
    "    df.drop(columns=['Spread',\n",
    "                     'Gap',\n",
    "                     'Intraday',\n",
    "                     ],\n",
    "            inplace=True)\n",
    "\n",
    "    df.drop(columns=['Open','High','Low','Close','Volume','Adj Close',\n",
    "                     ], inplace=True)\n",
    "    return df\n",
    "\n",
    "def process_features(df, lookback, step):\n",
    "    for i in range(step, lookback+1, step):\n",
    "        df['%d Spread' % (i)] = df['Spread'].pct_change(periods=i, fill_method=None)\n",
    "        df['%d Rolling Avg Spread' % (i)] = df['Spread'].rolling(window=i).mean()\n",
    "\n",
    "        df['%d Gap' % (i)] = df['Gap'].pct_change(periods=i, fill_method=None)\n",
    "        df['%d Rolling Avg Gap' % (i)] = df['Gap'].rolling(window=i).mean()\n",
    "\n",
    "        df['%d Intraday' % (i)] = df['Intraday'].pct_change(periods=i, fill_method=None)\n",
    "        df['%d Rolling Avg Intraday' % (i)] = df['Intraday'].rolling(window=i).mean()\n",
    "    return df\n",
    "\n",
    "def features(df, lookback, step):\n",
    "    create_features(df)\n",
    "    process_features(df, lookback, step)\n",
    "    drop_features(df)\n",
    "    return df\n",
    "\n",
    "def create_target(df, lookforward=2, target='Open'):\n",
    "    df['Target'] = np.log(df[target].shift(periods=-lookforward)/df[target].shift(periods=-1))\n",
    "    return df\n",
    "\n",
    "def custom_score(y_true, y_pred):\n",
    "  pred_sign = np.sign(y_pred)\n",
    "  y_true = np.squeeze(y_true)\n",
    "  returns = np.where((pred_sign == 1), y_true, 0)\n",
    "  return returns.mean()\n",
    "\n",
    "custom_scorer = make_scorer(custom_score, greater_is_better=True)"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "3. Define the models we are going to use"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "outputs": [],
   "source": [
    "estimator1 = Ridge()\n",
    "estimator2 = Lasso(alpha=.001)\n",
    "estimator3 = KNeighborsRegressor()\n",
    "models = [estimator1,estimator2,estimator3]\n",
    "estimator = VotingRegressor(estimators=[('Ridge', estimator1),\n",
    "                                        ('Lasso', estimator2),\n",
    "                                        ('KNN', estimator3),\n",
    "                                        ])"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "4. Define target, cross validation folds, interval, and lookback parameters."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "outputs": [],
   "source": [
    "lookforward = 2\n",
    "tscv = TimeSeriesSplit(n_splits=5, gap=lookforward)\n",
    "step = 2\n",
    "lookback = 2"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "5. Load data"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[*********************100%***********************]  1 of 1 completed\n",
      "[*********************100%***********************]  1 of 1 completed\n"
     ]
    }
   ],
   "source": [
    "spy = yf.download('SPY', start='2004-01-01')\n",
    "agg = yf.download('AGG', start='2004-01-01')\n",
    "\n",
    "spy = create_target(spy, lookforward, target='Open')\n",
    "\n",
    "spy = features(spy, lookback, step)\n",
    "spy = spy.add_suffix(' SPY')\n",
    "agg = features(agg, lookback, step)\n",
    "agg = agg.add_suffix(' AGG')\n",
    "cv = pd.merge(spy, agg, how='inner', on='Date')\n",
    "\n",
    "cv.drop(cv.tail(lookforward).index, inplace=True)\n",
    "cv.drop(cv.head(lookback).index, inplace=True)\n",
    "X = cv\n",
    "y = X[['Target SPY']]\n",
    "X = X.drop(columns=['Target SPY'])\n",
    "X.fillna(method=\"ffill\", inplace=True)\n",
    "X.replace([np.inf, -np.inf], 0, inplace=True)\n",
    "X.fillna(0, inplace=True)"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "6. Define parameter grid. (Look at models on sklearns docs to find which parameters of a model you can change)"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "outputs": [],
   "source": [
    "param_grid = {\n",
    "    'Ridge__alpha': loguniform(1e-5, 1e0),\n",
    "    'Lasso__alpha': loguniform(1e-5, 1e0),\n",
    "    'KNN__n_neighbors': randint(1,10),\n",
    "}"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "7. Split train and test data and run Random Search on train data"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fitting 5 folds for each of 100 candidates, totalling 500 fits\n"
     ]
    },
    {
     "data": {
      "text/plain": "RandomizedSearchCV(cv=TimeSeriesSplit(gap=2, max_train_size=None, n_splits=5, test_size=None),\n                   estimator=VotingRegressor(estimators=[('Ridge',\n                                                          Ridge(alpha=0.31856512010970556)),\n                                                         ('Lasso',\n                                                          Lasso(alpha=0.0011755154435882887)),\n                                                         ('KNN',\n                                                          KNeighborsRegressor(n_neighbors=4))]),\n                   n_iter=100, n_jobs=-1,\n                   param_distributions={'KNN__n_neighbors': <scipy.stats._distn_infrastructure.rv_frozen object at 0x0000017957BC6D00>,\n                                        'Lasso__alpha': <scipy.stats._distn_infrastructure.rv_frozen object at 0x0000017957BE3760>,\n                                        'Ridge__alpha': <scipy.stats._distn_infrastructure.rv_frozen object at 0x0000017957BC8430>},\n                   scoring=make_scorer(custom_score), verbose=1)",
      "text/html": "<style>#sk-container-id-2 {color: black;background-color: white;}#sk-container-id-2 pre{padding: 0;}#sk-container-id-2 div.sk-toggleable {background-color: white;}#sk-container-id-2 label.sk-toggleable__label {cursor: pointer;display: block;width: 100%;margin-bottom: 0;padding: 0.3em;box-sizing: border-box;text-align: center;}#sk-container-id-2 label.sk-toggleable__label-arrow:before {content: \"▸\";float: left;margin-right: 0.25em;color: #696969;}#sk-container-id-2 label.sk-toggleable__label-arrow:hover:before {color: black;}#sk-container-id-2 div.sk-estimator:hover label.sk-toggleable__label-arrow:before {color: black;}#sk-container-id-2 div.sk-toggleable__content {max-height: 0;max-width: 0;overflow: hidden;text-align: left;background-color: #f0f8ff;}#sk-container-id-2 div.sk-toggleable__content pre {margin: 0.2em;color: black;border-radius: 0.25em;background-color: #f0f8ff;}#sk-container-id-2 input.sk-toggleable__control:checked~div.sk-toggleable__content {max-height: 200px;max-width: 100%;overflow: auto;}#sk-container-id-2 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {content: \"▾\";}#sk-container-id-2 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-2 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-2 input.sk-hidden--visually {border: 0;clip: rect(1px 1px 1px 1px);clip: rect(1px, 1px, 1px, 1px);height: 1px;margin: -1px;overflow: hidden;padding: 0;position: absolute;width: 1px;}#sk-container-id-2 div.sk-estimator {font-family: monospace;background-color: #f0f8ff;border: 1px dotted black;border-radius: 0.25em;box-sizing: border-box;margin-bottom: 0.5em;}#sk-container-id-2 div.sk-estimator:hover {background-color: #d4ebff;}#sk-container-id-2 div.sk-parallel-item::after {content: \"\";width: 100%;border-bottom: 1px solid gray;flex-grow: 1;}#sk-container-id-2 div.sk-label:hover label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-2 div.sk-serial::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 0;bottom: 0;left: 50%;z-index: 0;}#sk-container-id-2 div.sk-serial {display: flex;flex-direction: column;align-items: center;background-color: white;padding-right: 0.2em;padding-left: 0.2em;position: relative;}#sk-container-id-2 div.sk-item {position: relative;z-index: 1;}#sk-container-id-2 div.sk-parallel {display: flex;align-items: stretch;justify-content: center;background-color: white;position: relative;}#sk-container-id-2 div.sk-item::before, #sk-container-id-2 div.sk-parallel-item::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 0;bottom: 0;left: 50%;z-index: -1;}#sk-container-id-2 div.sk-parallel-item {display: flex;flex-direction: column;z-index: 1;position: relative;background-color: white;}#sk-container-id-2 div.sk-parallel-item:first-child::after {align-self: flex-end;width: 50%;}#sk-container-id-2 div.sk-parallel-item:last-child::after {align-self: flex-start;width: 50%;}#sk-container-id-2 div.sk-parallel-item:only-child::after {width: 0;}#sk-container-id-2 div.sk-dashed-wrapped {border: 1px dashed gray;margin: 0 0.4em 0.5em 0.4em;box-sizing: border-box;padding-bottom: 0.4em;background-color: white;}#sk-container-id-2 div.sk-label label {font-family: monospace;font-weight: bold;display: inline-block;line-height: 1.2em;}#sk-container-id-2 div.sk-label-container {text-align: center;}#sk-container-id-2 div.sk-container {/* jupyter's `normalize.less` sets `[hidden] { display: none; }` but bootstrap.min.css set `[hidden] { display: none !important; }` so we also need the `!important` here to be able to override the default hidden behavior on the sphinx rendered scikit-learn.org. See: https://github.com/scikit-learn/scikit-learn/issues/21755 */display: inline-block !important;position: relative;}#sk-container-id-2 div.sk-text-repr-fallback {display: none;}</style><div id=\"sk-container-id-2\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>RandomizedSearchCV(cv=TimeSeriesSplit(gap=2, max_train_size=None, n_splits=5, test_size=None),\n                   estimator=VotingRegressor(estimators=[(&#x27;Ridge&#x27;,\n                                                          Ridge(alpha=0.31856512010970556)),\n                                                         (&#x27;Lasso&#x27;,\n                                                          Lasso(alpha=0.0011755154435882887)),\n                                                         (&#x27;KNN&#x27;,\n                                                          KNeighborsRegressor(n_neighbors=4))]),\n                   n_iter=100, n_jobs=-1,\n                   param_distributions={&#x27;KNN__n_neighbors&#x27;: &lt;scipy.stats._distn_infrastructure.rv_frozen object at 0x0000017957BC6D00&gt;,\n                                        &#x27;Lasso__alpha&#x27;: &lt;scipy.stats._distn_infrastructure.rv_frozen object at 0x0000017957BE3760&gt;,\n                                        &#x27;Ridge__alpha&#x27;: &lt;scipy.stats._distn_infrastructure.rv_frozen object at 0x0000017957BC8430&gt;},\n                   scoring=make_scorer(custom_score), verbose=1)</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item sk-dashed-wrapped\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-6\" type=\"checkbox\" ><label for=\"sk-estimator-id-6\" class=\"sk-toggleable__label sk-toggleable__label-arrow\">RandomizedSearchCV</label><div class=\"sk-toggleable__content\"><pre>RandomizedSearchCV(cv=TimeSeriesSplit(gap=2, max_train_size=None, n_splits=5, test_size=None),\n                   estimator=VotingRegressor(estimators=[(&#x27;Ridge&#x27;,\n                                                          Ridge(alpha=0.31856512010970556)),\n                                                         (&#x27;Lasso&#x27;,\n                                                          Lasso(alpha=0.0011755154435882887)),\n                                                         (&#x27;KNN&#x27;,\n                                                          KNeighborsRegressor(n_neighbors=4))]),\n                   n_iter=100, n_jobs=-1,\n                   param_distributions={&#x27;KNN__n_neighbors&#x27;: &lt;scipy.stats._distn_infrastructure.rv_frozen object at 0x0000017957BC6D00&gt;,\n                                        &#x27;Lasso__alpha&#x27;: &lt;scipy.stats._distn_infrastructure.rv_frozen object at 0x0000017957BE3760&gt;,\n                                        &#x27;Ridge__alpha&#x27;: &lt;scipy.stats._distn_infrastructure.rv_frozen object at 0x0000017957BC8430&gt;},\n                   scoring=make_scorer(custom_score), verbose=1)</pre></div></div></div><div class=\"sk-parallel\"><div class=\"sk-parallel-item\"><div class=\"sk-item\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-7\" type=\"checkbox\" ><label for=\"sk-estimator-id-7\" class=\"sk-toggleable__label sk-toggleable__label-arrow\">estimator: VotingRegressor</label><div class=\"sk-toggleable__content\"><pre>VotingRegressor(estimators=[(&#x27;Ridge&#x27;, Ridge(alpha=0.31856512010970556)),\n                            (&#x27;Lasso&#x27;, Lasso(alpha=0.0011755154435882887)),\n                            (&#x27;KNN&#x27;, KNeighborsRegressor(n_neighbors=4))])</pre></div></div></div><div class=\"sk-serial\"><div class=\"sk-item sk-dashed-wrapped\"><div class=\"sk-parallel\"><div class=\"sk-parallel-item\"><div class=\"sk-item\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><label>Ridge</label></div></div><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-estimator sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-8\" type=\"checkbox\" ><label for=\"sk-estimator-id-8\" class=\"sk-toggleable__label sk-toggleable__label-arrow\">Ridge</label><div class=\"sk-toggleable__content\"><pre>Ridge(alpha=0.31856512010970556)</pre></div></div></div></div></div></div><div class=\"sk-parallel-item\"><div class=\"sk-item\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><label>Lasso</label></div></div><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-estimator sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-9\" type=\"checkbox\" ><label for=\"sk-estimator-id-9\" class=\"sk-toggleable__label sk-toggleable__label-arrow\">Lasso</label><div class=\"sk-toggleable__content\"><pre>Lasso(alpha=0.0011755154435882887)</pre></div></div></div></div></div></div><div class=\"sk-parallel-item\"><div class=\"sk-item\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><label>KNN</label></div></div><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-estimator sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-10\" type=\"checkbox\" ><label for=\"sk-estimator-id-10\" class=\"sk-toggleable__label sk-toggleable__label-arrow\">KNeighborsRegressor</label><div class=\"sk-toggleable__content\"><pre>KNeighborsRegressor(n_neighbors=4)</pre></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div>"
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.85, shuffle=False)\n",
    "search = RandomizedSearchCV(estimator, param_distributions=param_grid, n_iter=100, cv=tscv, scoring=custom_scorer, n_jobs=-1, verbose=1)\n",
    "search.fit(X_train, y_train)"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "8. Print best parameters"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'KNN__n_neighbors': 8, 'Lasso__alpha': 0.002999525583333498, 'Ridge__alpha': 0.002840026017965097}\n",
      "0.00035391068997479727\n"
     ]
    }
   ],
   "source": [
    "print(search.best_params_)\n",
    "print(search.best_score_)"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "9. Backtest parameters"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      ""
     ]
    },
    {
     "data": {
      "text/html": "\n    <div class=\"bk-root\">\n        <a href=\"https://bokeh.org\" target=\"_blank\" class=\"bk-logo bk-logo-small bk-logo-notebook\"></a>\n        <span id=\"1002\">Loading BokehJS ...</span>\n    </div>"
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/javascript": "\n(function(root) {\n  function now() {\n    return new Date();\n  }\n\n  const force = true;\n\n  if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n    root._bokeh_onload_callbacks = [];\n    root._bokeh_is_loading = undefined;\n  }\n\n  const JS_MIME_TYPE = 'application/javascript';\n  const HTML_MIME_TYPE = 'text/html';\n  const EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n  const CLASS_NAME = 'output_bokeh rendered_html';\n\n  /**\n   * Render data to the DOM node\n   */\n  function render(props, node) {\n    const script = document.createElement(\"script\");\n    node.appendChild(script);\n  }\n\n  /**\n   * Handle when an output is cleared or removed\n   */\n  function handleClearOutput(event, handle) {\n    const cell = handle.cell;\n\n    const id = cell.output_area._bokeh_element_id;\n    const server_id = cell.output_area._bokeh_server_id;\n    // Clean up Bokeh references\n    if (id != null && id in Bokeh.index) {\n      Bokeh.index[id].model.document.clear();\n      delete Bokeh.index[id];\n    }\n\n    if (server_id !== undefined) {\n      // Clean up Bokeh references\n      const cmd_clean = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n      cell.notebook.kernel.execute(cmd_clean, {\n        iopub: {\n          output: function(msg) {\n            const id = msg.content.text.trim();\n            if (id in Bokeh.index) {\n              Bokeh.index[id].model.document.clear();\n              delete Bokeh.index[id];\n            }\n          }\n        }\n      });\n      // Destroy server and session\n      const cmd_destroy = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n      cell.notebook.kernel.execute(cmd_destroy);\n    }\n  }\n\n  /**\n   * Handle when a new output is added\n   */\n  function handleAddOutput(event, handle) {\n    const output_area = handle.output_area;\n    const output = handle.output;\n\n    // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n    if ((output.output_type != \"display_data\") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {\n      return\n    }\n\n    const toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n\n    if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n      toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n      // store reference to embed id on output_area\n      output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n    }\n    if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n      const bk_div = document.createElement(\"div\");\n      bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n      const script_attrs = bk_div.children[0].attributes;\n      for (let i = 0; i < script_attrs.length; i++) {\n        toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n        toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent\n      }\n      // store reference to server id on output_area\n      output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n    }\n  }\n\n  function register_renderer(events, OutputArea) {\n\n    function append_mime(data, metadata, element) {\n      // create a DOM node to render to\n      const toinsert = this.create_output_subarea(\n        metadata,\n        CLASS_NAME,\n        EXEC_MIME_TYPE\n      );\n      this.keyboard_manager.register_events(toinsert);\n      // Render to node\n      const props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n      render(props, toinsert[toinsert.length - 1]);\n      element.append(toinsert);\n      return toinsert\n    }\n\n    /* Handle when an output is cleared or removed */\n    events.on('clear_output.CodeCell', handleClearOutput);\n    events.on('delete.Cell', handleClearOutput);\n\n    /* Handle when a new output is added */\n    events.on('output_added.OutputArea', handleAddOutput);\n\n    /**\n     * Register the mime type and append_mime function with output_area\n     */\n    OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n      /* Is output safe? */\n      safe: true,\n      /* Index of renderer in `output_area.display_order` */\n      index: 0\n    });\n  }\n\n  // register the mime type if in Jupyter Notebook environment and previously unregistered\n  if (root.Jupyter !== undefined) {\n    const events = require('base/js/events');\n    const OutputArea = require('notebook/js/outputarea').OutputArea;\n\n    if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n      register_renderer(events, OutputArea);\n    }\n  }\n\n  \n  if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n    root._bokeh_timeout = Date.now() + 5000;\n    root._bokeh_failed_load = false;\n  }\n\n  const NB_LOAD_WARNING = {'data': {'text/html':\n     \"<div style='background-color: #fdd'>\\n\"+\n     \"<p>\\n\"+\n     \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n     \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n     \"</p>\\n\"+\n     \"<ul>\\n\"+\n     \"<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n\"+\n     \"<li>use INLINE resources instead, as so:</li>\\n\"+\n     \"</ul>\\n\"+\n     \"<code>\\n\"+\n     \"from bokeh.resources import INLINE\\n\"+\n     \"output_notebook(resources=INLINE)\\n\"+\n     \"</code>\\n\"+\n     \"</div>\"}};\n\n  function display_loaded() {\n    const el = document.getElementById(\"1002\");\n    if (el != null) {\n      el.textContent = \"BokehJS is loading...\";\n    }\n    if (root.Bokeh !== undefined) {\n      if (el != null) {\n        el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n      }\n    } else if (Date.now() < root._bokeh_timeout) {\n      setTimeout(display_loaded, 100)\n    }\n  }\n\n\n  function run_callbacks() {\n    try {\n      root._bokeh_onload_callbacks.forEach(function(callback) {\n        if (callback != null)\n          callback();\n      });\n    } finally {\n      delete root._bokeh_onload_callbacks\n    }\n    console.debug(\"Bokeh: all callbacks have finished\");\n  }\n\n  function load_libs(css_urls, js_urls, callback) {\n    if (css_urls == null) css_urls = [];\n    if (js_urls == null) js_urls = [];\n\n    root._bokeh_onload_callbacks.push(callback);\n    if (root._bokeh_is_loading > 0) {\n      console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n      return null;\n    }\n    if (js_urls == null || js_urls.length === 0) {\n      run_callbacks();\n      return null;\n    }\n    console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n    root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n    function on_load() {\n      root._bokeh_is_loading--;\n      if (root._bokeh_is_loading === 0) {\n        console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n        run_callbacks()\n      }\n    }\n\n    function on_error(url) {\n      console.error(\"failed to load \" + url);\n    }\n\n    for (let i = 0; i < css_urls.length; i++) {\n      const url = css_urls[i];\n      const element = document.createElement(\"link\");\n      element.onload = on_load;\n      element.onerror = on_error.bind(null, url);\n      element.rel = \"stylesheet\";\n      element.type = \"text/css\";\n      element.href = url;\n      console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n      document.body.appendChild(element);\n    }\n\n    for (let i = 0; i < js_urls.length; i++) {\n      const url = js_urls[i];\n      const element = document.createElement('script');\n      element.onload = on_load;\n      element.onerror = on_error.bind(null, url);\n      element.async = false;\n      element.src = url;\n      console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n      document.head.appendChild(element);\n    }\n  };\n\n  function inject_raw_css(css) {\n    const element = document.createElement(\"style\");\n    element.appendChild(document.createTextNode(css));\n    document.body.appendChild(element);\n  }\n\n  \n  const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.2.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.2.min.js\"];\n  const css_urls = [];\n  \n\n  const inline_js = [\n    function(Bokeh) {\n      Bokeh.set_log_level(\"info\");\n    },\n    function(Bokeh) {\n    \n    \n    }\n  ];\n\n  function run_inline_js() {\n    \n    if (root.Bokeh !== undefined || force === true) {\n      \n    for (let i = 0; i < inline_js.length; i++) {\n      inline_js[i].call(root, root.Bokeh);\n    }\n    if (force === true) {\n        display_loaded();\n      }} else if (Date.now() < root._bokeh_timeout) {\n      setTimeout(run_inline_js, 100);\n    } else if (!root._bokeh_failed_load) {\n      console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n      root._bokeh_failed_load = true;\n    } else if (force !== true) {\n      const cell = $(document.getElementById(\"1002\")).parents('.cell').data().cell;\n      cell.output_area.append_execute_result(NB_LOAD_WARNING)\n    }\n\n  }\n\n  if (root._bokeh_is_loading === 0) {\n    console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n    run_inline_js();\n  } else {\n    load_libs(css_urls, js_urls, function() {\n      console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n      run_inline_js();\n    });\n  }\n}(window));",
      "application/vnd.bokehjs_load.v0+json": "\n(function(root) {\n  function now() {\n    return new Date();\n  }\n\n  const force = true;\n\n  if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n    root._bokeh_onload_callbacks = [];\n    root._bokeh_is_loading = undefined;\n  }\n\n  \n\n  \n  if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n    root._bokeh_timeout = Date.now() + 5000;\n    root._bokeh_failed_load = false;\n  }\n\n  const NB_LOAD_WARNING = {'data': {'text/html':\n     \"<div style='background-color: #fdd'>\\n\"+\n     \"<p>\\n\"+\n     \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n     \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n     \"</p>\\n\"+\n     \"<ul>\\n\"+\n     \"<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n\"+\n     \"<li>use INLINE resources instead, as so:</li>\\n\"+\n     \"</ul>\\n\"+\n     \"<code>\\n\"+\n     \"from bokeh.resources import INLINE\\n\"+\n     \"output_notebook(resources=INLINE)\\n\"+\n     \"</code>\\n\"+\n     \"</div>\"}};\n\n  function display_loaded() {\n    const el = document.getElementById(\"1002\");\n    if (el != null) {\n      el.textContent = \"BokehJS is loading...\";\n    }\n    if (root.Bokeh !== undefined) {\n      if (el != null) {\n        el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n      }\n    } else if (Date.now() < root._bokeh_timeout) {\n      setTimeout(display_loaded, 100)\n    }\n  }\n\n\n  function run_callbacks() {\n    try {\n      root._bokeh_onload_callbacks.forEach(function(callback) {\n        if (callback != null)\n          callback();\n      });\n    } finally {\n      delete root._bokeh_onload_callbacks\n    }\n    console.debug(\"Bokeh: all callbacks have finished\");\n  }\n\n  function load_libs(css_urls, js_urls, callback) {\n    if (css_urls == null) css_urls = [];\n    if (js_urls == null) js_urls = [];\n\n    root._bokeh_onload_callbacks.push(callback);\n    if (root._bokeh_is_loading > 0) {\n      console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n      return null;\n    }\n    if (js_urls == null || js_urls.length === 0) {\n      run_callbacks();\n      return null;\n    }\n    console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n    root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n    function on_load() {\n      root._bokeh_is_loading--;\n      if (root._bokeh_is_loading === 0) {\n        console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n        run_callbacks()\n      }\n    }\n\n    function on_error(url) {\n      console.error(\"failed to load \" + url);\n    }\n\n    for (let i = 0; i < css_urls.length; i++) {\n      const url = css_urls[i];\n      const element = document.createElement(\"link\");\n      element.onload = on_load;\n      element.onerror = on_error.bind(null, url);\n      element.rel = \"stylesheet\";\n      element.type = \"text/css\";\n      element.href = url;\n      console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n      document.body.appendChild(element);\n    }\n\n    for (let i = 0; i < js_urls.length; i++) {\n      const url = js_urls[i];\n      const element = document.createElement('script');\n      element.onload = on_load;\n      element.onerror = on_error.bind(null, url);\n      element.async = false;\n      element.src = url;\n      console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n      document.head.appendChild(element);\n    }\n  };\n\n  function inject_raw_css(css) {\n    const element = document.createElement(\"style\");\n    element.appendChild(document.createTextNode(css));\n    document.body.appendChild(element);\n  }\n\n  \n  const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.2.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.2.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.2.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.2.min.js\"];\n  const css_urls = [];\n  \n\n  const inline_js = [\n    function(Bokeh) {\n      Bokeh.set_log_level(\"info\");\n    },\n    function(Bokeh) {\n    \n    \n    }\n  ];\n\n  function run_inline_js() {\n    \n    if (root.Bokeh !== undefined || force === true) {\n      \n    for (let i = 0; i < inline_js.length; i++) {\n      inline_js[i].call(root, root.Bokeh);\n    }\n    if (force === true) {\n        display_loaded();\n      }} else if (Date.now() < root._bokeh_timeout) {\n      setTimeout(run_inline_js, 100);\n    } else if (!root._bokeh_failed_load) {\n      console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n      root._bokeh_failed_load = true;\n    } else if (force !== true) {\n      const cell = $(document.getElementById(\"1002\")).parents('.cell').data().cell;\n      cell.output_area.append_execute_result(NB_LOAD_WARNING)\n    }\n\n  }\n\n  if (root._bokeh_is_loading === 0) {\n    console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n    run_inline_js();\n  } else {\n    load_libs(css_urls, js_urls, function() {\n      console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n      run_inline_js();\n    });\n  }\n}(window));"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# !pip3 install backtesting\n",
    "from backtesting import Strategy, Backtest\n",
    "from sklearn.model_selection import train_test_split"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      ""
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[*********************100%***********************]  1 of 1 completed\n",
      "Start                     2020-01-24 00:00:00\n",
      "End                       2022-11-16 00:00:00\n",
      "Duration                   1027 days 00:00:00\n",
      "Exposure Time [%]                   99.718706\n",
      "Equity Final [$]                  1311.039856\n",
      "Equity Peak [$]                   1416.379425\n",
      "Return [%]                          31.103986\n",
      "Buy & Hold Return [%]               20.281664\n",
      "Return (Ann.) [%]                   10.074481\n",
      "Volatility (Ann.) [%]               24.580116\n",
      "Sharpe Ratio                         0.409863\n",
      "Sortino Ratio                        0.666017\n",
      "Calmar Ratio                         0.572078\n",
      "Max. Drawdown [%]                  -17.610312\n",
      "Avg. Drawdown [%]                   -3.194534\n",
      "Max. Drawdown Duration      250 days 00:00:00\n",
      "Avg. Drawdown Duration       34 days 00:00:00\n",
      "# Trades                                  709\n",
      "Win Rate [%]                        53.314528\n",
      "Best Trade [%]                       6.348897\n",
      "Worst Trade [%]                     -8.799434\n",
      "Avg. Trade [%]                       0.047016\n",
      "Max. Trade Duration           4 days 00:00:00\n",
      "Avg. Trade Duration           2 days 00:00:00\n",
      "Profit Factor                        1.120292\n",
      "Expectancy [%]                       0.057818\n",
      "SQN                                  0.806887\n",
      "_strategy                          MyStrategy\n",
      "_equity_curve                             ...\n",
      "_trades                        Size  Entry...\n",
      "dtype: object\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      ""
     ]
    }
   ],
   "source": [
    "step = 2\n",
    "lookback = 2\n",
    "\n",
    "estimator1 = Ridge(alpha=0.002840026017965097)\n",
    "estimator2 = Lasso(alpha=0.002999525583333498)\n",
    "estimator3 = KNeighborsRegressor(n_neighbors=8)\n",
    "models = [estimator1,\n",
    "          estimator2,\n",
    "          estimator3,\n",
    "          ]\n",
    "estimator = VotingRegressor(estimators=[('Ridge', estimator1),\n",
    "                                        ('Lasso', estimator2),\n",
    "                                        ('KNN', estimator3),\n",
    "                                        ],)\n",
    "\n",
    "X_test = X_test.iloc[(abs(lookforward)):]\n",
    "y_test = y_test.iloc[(abs(lookforward)):]\n",
    "\n",
    "estimator.fit(X_train, y_train)\n",
    "forecasted = estimator.predict(X_test)\n",
    "\n",
    "data = yf.download('SPY', start='2004-01-01')\n",
    "data.drop(data.tail(lookforward).index,inplace=True)\n",
    "data.drop(data.head(lookback).index,inplace=True)\n",
    "data = data.iloc[(-X_test.shape[0]):]\n",
    "data['forecastedValue'] = forecasted\n",
    "prediction = data\n",
    "\n",
    "class MyStrategy(Strategy):\n",
    "    Data = prediction\n",
    "\n",
    "    def init(self):\n",
    "        super().init()\n",
    "\n",
    "    def next(self):\n",
    "        if self.data.forecastedValue < 0:\n",
    "            self.sell()\n",
    "        elif self.data.forecastedValue > 0:\n",
    "            self.buy()\n",
    "\n",
    "\n",
    "bt = Backtest(prediction, MyStrategy,\n",
    "              cash=1000,\n",
    "              trade_on_close=False,\n",
    "              exclusive_orders=True\n",
    "              )\n",
    "print(bt.run())"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Surprisingly, the results are better than the previous notebook, despite that one cheating a little bit. The strategy beats buying and holding which is good. But would I run this strategy? No, but this is a good starting point.\n",
    "\n",
    "Somethings to explore futher: Create more features, add more data sources, evaluate more models, evaluate more parameters, evaluate higher period step interval and look back periods, evaluate how many cross validation folds are optimal when taking the bias-variance trade-off into account, backtest with commission, etc."
   ],
   "metadata": {
    "collapsed": false
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
